/*
 * @CreatDate: 2021-11-24 14:45:34
 * @Describe: 文字高亮组件
 */

import { createElement } from 'react';
import { findAll } from 'highlight-words-core';
import PropTypes from 'prop-types';
import memoizeOne from 'memoize-one';

Highlighter.propTypes = {
  activeClassName: PropTypes.string,
  activeIndex: PropTypes.number,
  activeStyle: PropTypes.object,
  autoEscape: PropTypes.bool,
  className: PropTypes.string,
  findChunks: PropTypes.func,
  highlightClassName: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  highlightStyle: PropTypes.object,
  highlightTag: PropTypes.oneOfType([
    PropTypes.node,
    PropTypes.func,
    PropTypes.string,
  ]),
  sanitize: PropTypes.func,
  searchWords: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(RegExp)]),
  ).isRequired,
  textToHighlight: PropTypes.string.isRequired,
  unhighlightClassName: PropTypes.string,
  unhighlightStyle: PropTypes.object,
};

/**
 * Highlights all occurrences of search terms (searchText) within a string (textToHighlight).
 * This function returns an array of strings and <span>s (wrapping highlighted words).
 */
export default function Highlighter({
  activeClassName = '',
  activeIndex = -1,
  activeStyle,
  autoEscape,
  caseSensitive = false,
  className,
  findChunks,
  highlightClassName = '',
  highlightStyle = {},
  highlightTag = 'mark',
  sanitize,
  searchWords,
  textToHighlight,
  unhighlightClassName = '',
  unhighlightStyle,
  ...rest
}) {
  const chunks = findAll({
    autoEscape,
    caseSensitive,
    findChunks,
    sanitize,
    searchWords,
    textToHighlight,
  });
  const HighlightTag = highlightTag;
  let highlightIndex = -1;
  let highlightClassNames = '';
  let highlightStyles;

  const lowercaseProps = object => {
    const mapped = {};
    for (let key in object) {
      mapped[key.toLowerCase()] = object[key];
    }
    return mapped;
  };
  const memoizedLowercaseProps = memoizeOne(lowercaseProps);

  return createElement('span', {
    className,
    ...rest,
    children: chunks.map((chunk, index) => {
      const text = textToHighlight.substr(chunk.start, chunk.end - chunk.start);

      if (chunk.highlight) {
        highlightIndex++;

        let highlightClass;
        if (typeof highlightClassName === 'object') {
          if (!caseSensitive) {
            highlightClassName = memoizedLowercaseProps(highlightClassName);
            highlightClass = highlightClassName[text.toLowerCase()];
          } else {
            highlightClass = highlightClassName[text];
          }
        } else {
          highlightClass = highlightClassName;
        }

        const isActive = highlightIndex === +activeIndex;

        highlightClassNames = `${highlightClass} ${
          isActive ? activeClassName : ''
        }`;
        highlightStyles =
          isActive === true && activeStyle != null
            ? Object.assign({}, highlightStyle, activeStyle)
            : highlightStyle;

        const props = {
          children: text,
          className: highlightClassNames,
          key: index,
          style: highlightStyles,
        };

        // Don't attach arbitrary props to DOM elements; this triggers React DEV warnings (https://fb.me/react-unknown-prop)
        // Only pass through the highlightIndex attribute for custom components.
        if (typeof HighlightTag !== 'string') {
          props.highlightIndex = highlightIndex;
        }

        return createElement(HighlightTag, props);
      } else {
        return createElement('span', {
          children: text,
          className: unhighlightClassName,
          key: index,
          style: unhighlightStyle,
        });
      }
    }),
  });
}
